// Fill out your copyright notice in the Description page of Project Settings.

#pragma once
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "CoreMinimal.h"

DECLARE_DELEGATE(FStateThreadDelegate)

namespace EThreadState
{
	enum Type
	{
		Init,

		Run,

		Complete,

		Stop,

		Join,

		Exit
	};

}
/**
 *
 */
class FStateThread final : public FRunnable
{
public:
	using FCallback = TFunction<void()>;

	explicit FStateThread(const TCHAR* InThreadName, const FCallback& InRunFunc) : ThreadName(InThreadName), CallBack(InRunFunc), ThreadState(EThreadState::Init) {}

	virtual void Execute()
	{
		if (ThreadState == EThreadState::Init)
		{
			WorkThread.Reset(FRunnableThread::Create(this, *ThreadName, 0));
			if (WorkThread.IsValid())
				ThreadState = EThreadState::Run;
		}
	}

	virtual void Join()
	{
		ThreadState = EThreadState::Join;
		StateJoinDelagete.ExecuteIfBound();
		WorkThread->WaitForCompletion();
	}

	virtual void Stop() override
	{
		ThreadState = EThreadState::Stop;
		StateStopDelagete.ExecuteIfBound();
	}

	virtual void Exit() override
	{
		ThreadState = EThreadState::Exit;
		StateExitDelagete.ExecuteIfBound();
	}

	virtual uint32 Run() override
	{
		ThreadState = EThreadState::Run;
		StateRunDelagete.ExecuteIfBound();
		CallBack();
		ThreadState = EThreadState::Complete;
		StateCompleteDelagete.ExecuteIfBound();
		return 0;
	}

	virtual EThreadState::Type GetThreadState() const
	{
		return ThreadState;
	}

	template<class UserClass>
	FStateThread& StateRun(UserClass* UserObj, typename FStateThreadDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod)
	{
		StateRunDelagete.BindRaw(UserObj, InMethod);
		return *this;
	}

	template<class UserClass>
	FStateThread& StateComplete(UserClass* UserObj, typename FStateThreadDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod)
	{
		StateCompleteDelagete.BindRaw(UserObj, InMethod);
		return *this;
	}

	template<class UserClass>
	FStateThread& StateStop(UserClass* UserObj, typename FStateThreadDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod)
	{
		StateStopDelagete.BindRaw(UserObj, InMethod);
		return *this;
	}

	template<class UserClass>
	FStateThread& StateJoin(UserClass* UserObj, typename FStateThreadDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod)
	{
		StateJoinDelagete.BindRaw(UserObj, InMethod);
		return *this;
	}

	template<class UserClass>
	FStateThread& StateExit(UserClass* UserObj, typename FStateThreadDelegate::TRawMethodDelegate<UserClass>::FMethodPtr InMethod)
	{
		StateExitDelagete.BindRaw(UserObj, InMethod);
		return *this;
	}

protected:

	FString ThreadName;
	volatile EThreadState::Type ThreadState;
	TUniquePtr<FRunnableThread> WorkThread;
	FCallback CallBack;

	FStateThreadDelegate StateRunDelagete;
	FStateThreadDelegate StateCompleteDelagete;
	FStateThreadDelegate StateStopDelagete;
	FStateThreadDelegate StateJoinDelagete;
	FStateThreadDelegate StateExitDelagete;

private:
	FStateThread(const FStateThread&) = delete;
	FStateThread& operator=(const FStateThread&) = delete;
};
